package com.biwin.module.security.starter.handler;

import cn.hutool.core.collection.ListUtil;
import com.biwin.core.main.property.BiWinCheckUrlProperties;
import com.biwin.core.main.property.BiWinLoginProperties;
import com.biwin.core.main.property.BiWinProperties;
import com.biwin.module.security.api.properties.BiWinSecurityProperties;
import com.biwin.module.security.api.vo.BwUserDetailVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * Security Metadata Source
 * <p>
 * Url 请求默认会先进入该类,而该类中如果返回null,则表明当次请求 Url 不需要任何权限,直接被放过
 *
 * @author biwin
 * @since 0.0.1
 */
@Component
public class BwFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private static final Logger log = LoggerFactory.getLogger(BwFilterInvocationSecurityMetadataSource.class);

    @Value("${server.error.path:/error}")
    private String path;

    @Autowired
    private BiWinSecurityProperties securityProperties;
    @Autowired
    private BiWinProperties biWinProperties;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        // pass all API's paths for developers
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(Objects.nonNull(principal) && principal instanceof BwUserDetailVo){
            BwUserDetailVo userDetailVo = (BwUserDetailVo) principal;
            if(userDetailVo.getId()==1 && userDetailVo.getUsername().equals(biWinProperties.getDevAccount())){
                if(log.isDebugEnabled()){
                    log.debug("当前登录用户为系统开发账号,将直接开放所有api权限...");
                }
                return null;
            }
        }
        // get request method url
        if (log.isDebugEnabled()) {
            FilterInvocation invocation = (FilterInvocation) o;
            log.debug("当前请求的地址: {}; 请求的方法: {}", invocation.getRequestUrl(), invocation.getHttpRequest().getMethod());
        }
        /**
         * 默认未登录情况下,只匹配项目中配置的所有需要忽略的地址接口.如果在忽略的列表中,则直接放过情况.
         *
         * 如果已登录,则仅获取当前用户的所有权限.
         */
        // check config file ignore urls setting
        if (checkAllConfigUrl(((FilterInvocation) o).getHttpRequest())) {
            return null;
        } else {
            //check url authorization

            // todo 可以通过扩展 AuthorizationFailureEvent 事件监听,处理非法破解请求,把ip加入黑名单,请求url加入黑名单等
            return SecurityConfig.createList("NO_PERMISSIONS");
        }
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    private boolean checkAllConfigUrl(HttpServletRequest request) {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        if (Objects.isNull(httpMethod)) {
            httpMethod = HttpMethod.GET;
        }
        BiWinCheckUrlProperties checkUrlProperties = securityProperties.getIgnore();
        Set<String> allUrl = new HashSet<>();
        switch (httpMethod) {
            case GET:
                allUrl.addAll(checkUrlProperties.getGet());
                break;
            case PUT:
                allUrl.addAll(checkUrlProperties.getPut());
                break;
            case HEAD:
                allUrl.addAll(checkUrlProperties.getHead());
                break;
            case POST:
                allUrl.addAll(checkUrlProperties.getPost());
                break;
            case PATCH:
                allUrl.addAll(checkUrlProperties.getPatch());
                break;
            case TRACE:
                allUrl.addAll(checkUrlProperties.getTrace());
                break;
            case DELETE:
                allUrl.addAll(checkUrlProperties.getDelete());
                break;
            case OPTIONS:
                allUrl.addAll(checkUrlProperties.getOptions());
                break;
            default:
                break;
        }
        allUrl.addAll(checkUrlProperties.getPattern());
        allUrl.addAll(ListUtil.toList(getPath()));

        if (!CollectionUtils.isEmpty(allUrl)) {
            for (String ignore : allUrl) {
                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);
                if (matcher.matches(request)) {
                    return true;
                }
            }
        }

        return false;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

